#include "http.hpp"

namespace http
{
    const std::unordered_set<std::string_view> http_server::_ip_any = { "0", "0.0", "0.0.0", "0.0.0.0" };
    const std::unordered_map<std::string_view, std::string_view> http_server::_mime_table = 
    {
        { ".html", "text/html" }, 
        { ".css", "text/css" }, 
        { ".js", "text/javascript" }, 
        { ".ico", "image/x-icon" }, 
        { ".json", "application/json" }
    };

    buffer::iterator::iterator(const buffer& buffer) noexcept
        : _buf(buffer)
        , _data(buffer._data_end)
    {}

    buffer::iterator::iterator(const buffer& buffer, char* data) noexcept
        : _buf(buffer)
        , _data(data)
    {}

    buffer::iterator::iterator(const iterator& it) noexcept
        : _buf(it._buf)
        , _data(it._data)
    {}

    buffer::iterator& buffer::iterator::operator=(const iterator& it)
    {
        if (&_buf != &it._buf)
        {
            throw general_err{ "different buffer" };
        }
        _data = it._data;
        return *this;
    }

    buffer::iterator& buffer::iterator::operator++() noexcept
    {
        add(1);
        return *this;
    }

    buffer::iterator& buffer::iterator::operator--() noexcept
    {
        sub(1);
        return *this;
    }

    buffer::iterator buffer::iterator::operator++(int) noexcept
    {
        iterator it(*this);
        add(1);
        return it;
    }

    buffer::iterator buffer::iterator::operator--(int) noexcept
    {
        iterator it(*this);
        sub(1);
        return it;
    }

    buffer::iterator buffer::iterator::operator+(size_t pos) const noexcept
    {
        iterator it(*this);
        it.add(pos);
        return it;
    }

    buffer::iterator buffer::iterator::operator-(size_t pos) const noexcept
    {
        iterator it(*this);
        it.sub(pos);
        return it;
    }

    size_t buffer::iterator::operator-(const iterator& it) const noexcept
    {
        if (*this >= it)
        {
            if (_data < it._data)
            {
                return _data - _buf._buffer_begin + _buf._buffer_end - it._data;
            }
            else
            {
                return _data - it._data;
            }
        }
        else
        {
            if (it._data < _data)
            {
                return it._data - _buf._buffer_begin + _buf._buffer_end - _data;
            }
            else
            {
                return it._data - _data;
            }
        }
    }

    buffer::iterator& buffer::iterator::operator+=(size_t pos) noexcept
    {
        add(pos);
        return *this;
    }

    buffer::iterator& buffer::iterator::operator-=(size_t pos) noexcept
    {
        sub(pos);
        return *this;
    }

    char& buffer::iterator::operator*()
    {
        return *_data;
    }

    char* buffer::iterator::operator->()
    {
        return _data;
    }

    bool buffer::iterator::operator<(const iterator& other) const
    {
        if (&_buf != &other._buf)
        {
            throw general_err{ "different buffer" };
        }
        if (_buf._data_begin <= _buf._data_end)
        {
            return _data < other._data;
        }
        else
        {
            if (_data > _buf._data_begin && other._data > _buf._data_begin)
            {
                return _data < other._data;
            }
            else if (_data < _buf._data_end&& other._data < _buf._data_end)
            {
                return _data < other._data;
            }
            else
            {
                return _data > _buf._data_begin;
            }
        }
    }

    bool buffer::iterator::operator>(const iterator& other) const
    {
        if (&_buf != &other._buf)
        {
            throw general_err{ "different buffer" };
        }
        if (_buf._data_begin <= _buf._data_end)
        {
            return _data > other._data;
        }
        else
        {
            if (_data > _buf._data_begin && other._data > _buf._data_begin)
            {
                return _data > other._data;
            }
            else if (_data < _buf._data_end && other._data < _buf._data_end)
            {
                return _data > other._data;
            }
            else
            {
                return _data < _buf._data_end;
            }
        }
    }

    bool buffer::iterator::operator==(const iterator& other) const
    {
        if (&_buf != &other._buf)
        {
            throw general_err{ "different buffer" };
        }
        return _data == other._data;
    }

    bool buffer::iterator::operator<=(const iterator& other) const
    {
        return *this < other || *this == other;
    }

    bool buffer::iterator::operator>=(const iterator& other) const
    {
        return *this > other || *this == other;
    }

    bool buffer::iterator::operator!=(const iterator& other) const
    {
        return !(*this == other);
    }

    void buffer::iterator::add(size_t pos)
    {
        if (_buf._data_begin <= _buf._data_end)
        {
            if (pos > static_cast<size_t>(_buf._data_end - _data))
            {
                throw general_err{ "pos overflow" };
            }
            _data += pos;
        }
        else
        {
            if (_data >= _buf._data_begin)
            {
                if (pos > static_cast<size_t>((_buf._buffer_end - _data) + (_buf._data_end - _buf._buffer_begin)))
                {
                    throw general_err{ "pos overflow" };
                }
                if (pos < static_cast<size_t>((_buf._buffer_end - _data)))
                {
                    _data += pos;
                }
                else
                {
                    _data = _buf._buffer_begin + (pos - (_buf._buffer_end - _data));
                }
            }
            else if (_data <= _buf._data_end)
            {
                if (pos > static_cast<size_t>(_buf._data_end - _data))
                {
                    throw general_err{ "pos overflow" };
                }
                _data += pos;
            }
        }
    }

    void buffer::iterator::sub(size_t pos)
    {
        if (_buf._data_begin <= _buf._data_end)
        {
            if (pos > static_cast<size_t>(_data - _buf._data_begin))
            {
                throw general_err{ "pos overflow" };
            }
            _data -= pos;
        }
        else
        {
            if (_data <= _buf._data_end)
            {
                if (pos > static_cast<size_t>((_data - _buf._buffer_begin) + (_buf._buffer_end - _buf._data_begin)))
                {
                    throw general_err{ "pos overflow" };
                }
                if (pos >= static_cast<size_t>((_data - _buf._buffer_begin)))
                {
                    _data -= pos;
                }
                else
                {
                    _data = _buf._buffer_end - (pos - (_data - _buf._buffer_begin));
                }
            }
            else if (_data >= _buf._data_begin)
            {
                if (pos > static_cast<size_t>(_data - _buf._data_begin))
                {
                    throw general_err{ "pos overflow" };
                }
                _data -= pos;
            }
        }
    }

    buffer::buffer()
        : _buffer_begin(new char[BUFFER_LENGTH + 1])
        , _buffer_end(_buffer_begin + BUFFER_LENGTH + 1)
        , _data_begin(_buffer_begin)
        , _data_end(_buffer_begin)
    {}

    buffer::~buffer()
    {
        delete[] _buffer_begin;
    }

    void buffer::reset() noexcept
    {
        _data_begin = _buffer_begin;
        _data_end = _buffer_end;
    }

    size_t buffer::getNextContinueUninitializedSpaceSize()
    {
        if (_data_begin > _data_end)
        {
            return _data_begin - _data_end - 1;
        }
        else
        {
            return _buffer_end - _data_end - 1;
        }
    }

    void buffer::movePtr(char*& ptr, size_t pos) noexcept
    {
        size_t tem = _buffer_end - ptr;
        if (tem <= pos)
        {
            ptr = _buffer_begin;
            ptr += pos - tem;
        }
        else
        {
            ptr += pos;
        }
    }

    buffer::iterator buffer::begin() noexcept
    {
        return iterator{ *this, _data_begin };
    }

    buffer::iterator buffer::end() noexcept
    {
        return iterator{ *this, _data_end };
    }

    std::string http_res::serialize()
    {
        std::string serialized_str;
        serialized_str += _head._version;
        serialized_str += ' ';
        serialized_str += std::to_string(_head._code);
        serialized_str += ' ';
        serialized_str += _head._status;
        serialized_str += "\r\n";
        for (auto& kv : _property._data)
        {
            serialized_str += kv.first;
            serialized_str += ": ";
            serialized_str += kv.second;
            serialized_str += "\r\n";
        }
        serialized_str += "\r\n";
        serialized_str += _content._data;
        return serialized_str;
    }

    bool http_req::isComplated()
    {
        return _head._is_filled && _property._is_filled && _content._is_filled;
    }

    void http_req::clear()
    {
        _head._is_filled = false;
        _head._method.resize(0);
        _head._url.resize(0);
        _head._version.resize(0);
        _property._is_filled = false;
        _property._data.clear();
        _content._is_filled = false;
        _content._data.resize(0);
    }

    void http_req::deSerialize(buffer& buf)
    {
        auto cur = buf.begin();
        auto cur_next = buf.begin();
        auto cur_start = buf.begin();
        if (_head._is_filled && _property._is_filled && _content._is_filled)
        {
            throw general_err{ "http request is completed" };
        }
        if (cur != buf.end() && !_head._is_filled)
        {
            std::string* dest_ptr = &_head._method;
            size_t which = 0;
            while (cur != buf.end())
            {
                if (*cur == '\r')
                {
                    cur_next = cur + 1;
                    if (cur_next != buf.end() && *cur_next == '\n')
                    {
                        _head._is_filled = true;
                        cur += 2;
                        cur_start = cur;
                        buf._data_begin = cur.operator->();
                        break;
                    }
                }
                else if (*cur == ' ')
                {
                    switch (which)
                    {
                    case 0:
                        dest_ptr = &_head._url;
                        break;
                    case 1:
                        dest_ptr = &_head._version;
                        break;
                    }
                    ++which;
                    ++cur;
                }
                else
                {
                    dest_ptr->push_back(*cur);
                    ++cur;
                }
            }
        }
        if (cur != buf.end() && !_property._is_filled)
        {
            std::string key_buf;
            std::string value_buf;
            while (cur != buf.end())
            {
                if (*cur == '\r')
                {
                    cur_next = cur + 1;
                    if (cur_next != buf.end() && *cur_next == '\n')
                    {
                        auto seperater = cur_start;
                        while (*seperater != ':')
                        {
                            ++seperater;
                        }
                        key_buf.resize(seperater - cur_start);
                        value_buf.resize(cur - seperater - 2);
                        for (size_t i = 0; i < key_buf.size(); ++i, ++cur_start)
                        {
                            key_buf[i] = *cur_start;
                        }
                        cur_start = seperater + 2;
                        for (size_t i = 0; i < value_buf.size(); ++i, ++cur_start)
                        {
                            value_buf[i] = *cur_start;
                        }
                        _property._data.emplace(std::make_pair(std::move(key_buf), std::move(value_buf)));
                        cur += 2;
                        cur_start = cur;
                        buf._data_begin = cur.operator->();
                    }
                    if (cur != buf.end() && *cur == '\r')
                    {
                        cur_next = cur + 1;
                        if (cur_next != buf.end() && *cur_next == '\n')
                        {
                            _property._is_filled = true;
                            cur += 2;
                            cur_start = cur;
                            buf._data_begin = cur.operator->();
                            break;
                        }
                    }
                }
                else
                {
                    ++cur;
                }
            }
        }
        if (_property._data.find("Content-Length") == _property._data.end())
        {
            _content._is_filled = true;
        }
        else if (cur != buf.end() && !_content._is_filled)
        {
            size_t content_length = std::stoull(_property._data["Content-Length"]);
            _content._data.resize(content_length);
            size_t written_char = 0;
            for (written_char = 0; written_char < content_length && cur != buf.end(); ++written_char, ++cur)
            {
                _content._data[written_char] = *cur;
            }
            if (written_char == content_length)
            {
                _content._is_filled = true;
                buf._data_begin = cur.operator->();
            }
        }
    }

    http_server::http_server(short port, const std::string& ip)
        : _server_msg()
        , _server_msg_len(0)
    {
        try
        {
            _server_msg = my_socket::internetStructInit(AF_INET, port,
                _ip_any.find(ip) != _ip_any.end() ? ip.data() : INADDR_ANY);
        }
        catch(const std::exception& e)
        {
            throw e;
        }
        _server_msg_len = sizeof(_server_msg);
    }

    void http_server::start()
    {
        int ret = 0;
        int fd = socket(AF_INET, SOCK_STREAM, 0);
        if (fd == -1)
        {
            throw general_err{ strerror(errno) };
        }
        ret = bind(fd, reinterpret_cast<const sockaddr*>(&_server_msg), _server_msg_len);
        if (ret == -1)
        {
            throw general_err{ strerror(errno) };
        }
        ret = listen(fd, 4);
        if (ret == -1)
        {
            throw general_err{ strerror(errno) };
        }
        loop(fd);
    }

    void http_server::loop(int fd)
    {
        int connection_fd = 0;
        sockaddr_in client_msg{};
        socklen_t client_msg_len = 0;
        auto f = std::bind(&http_server::connection, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
        while (1)
        {
            connection_fd = accept(fd, reinterpret_cast<sockaddr*>(&client_msg), &client_msg_len);
            if (connection_fd == -1)
            {
                throw general_err{ strerror(errno) };
            }
            std::jthread task(f, connection_fd, client_msg, client_msg_len);
            task.detach();
        }
    }

    http_res http_server::parse(http_req& request)
    {
        static const std::string root = "./webroot";
        std::filesystem::path resource_path = root;
        unsigned long long file_size = 0;
        http_res response;
        response._head._version = "HTTP/1.1";
        bool need_log = false;
        if (request._head._url == "/")
        {
            response._head._code = 200;
            response._head._status = "OK";
            resource_path += "/cargo.html";
        }
        else
        {
            resource_path += request._head._url;
            if (request._head._url == "/getData")
            {
                response._head._code = 200;
                response._head._status = "OK";
                need_log = true;
            }
            else if (resource_path.empty())
            {
                response._head._code = 404;
                response._head._status = "Not Found";
                resource_path = root + "/cargo.html";
            }
            else
            {
                response._head._code = 200;
                response._head._status = "OK";
            }
        }
        if (!need_log)
        {
            file_size = std::filesystem::file_size(resource_path);
            response._property._data["Content-Type"] = _mime_table.at(std::string{ resource_path.extension() });
            response._property._data["Content-Type"] += ";charset=utf-8";
            response._property._data["Content-Length"] = std::to_string(file_size);
            response._content._data.resize(file_size);
            std::fstream file(resource_path, std::ios::binary | std::ios::in);
            file.read(response._content._data.data(), file_size);
        }
        else
        {
            database::sql_client sql_data;
            sql_data.connect();
            response._content._data = std::move(sql_data.select());
            response._property._data["Content-Length"] = std::to_string(response._content._data.size());
            response._property._data["Content-Type"] = _mime_table.at(std::string{ ".json" });
            response._property._data["Content-Type"] += ";charset=utf-8";
        }
        return response;
    }

    void http_server::connection(int connection_fd, sockaddr_in client_msg, socklen_t client_msg_len)
    {
        buffer buf;
        http_req request;
        http_res response;
        std::string serialized_res;
        ssize_t operatored_byte = -1;
        size_t unsent_data = 0;
        while (operatored_byte)
        {
            while (!request.isComplated() && operatored_byte != 0)
            {
                operatored_byte = recv(connection_fd, buf._data_end, buf.getNextContinueUninitializedSpaceSize(), 0);
                if (operatored_byte == 0)
                {
                    break;
                }
                else if (operatored_byte == -1)
                {
                    throw general_err{ strerror(errno) };
                }
                buf.movePtr(buf._data_end, operatored_byte);
                request.deSerialize(buf);
            }
            if (operatored_byte != 0)
            {
                response = std::move(parse(request));
                serialized_res = std::move(response.serialize());
                unsent_data = serialized_res.size();
            }
            while (unsent_data && operatored_byte != 0)
            {
                operatored_byte = send(connection_fd, serialized_res.data() + (serialized_res.size() - unsent_data), unsent_data, 0);
                if (operatored_byte == 0)
                {
                    break;
                }
                else if (operatored_byte == -1)
                {
                    throw general_err{ strerror(errno) };
                }
                unsent_data -= operatored_byte;
            }
            request.clear();
        }
    }
}